package nl.harmidi.main;

import java.awt.BorderLayout;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.MenuShortcut;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.util.prefs.Preferences;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequencer;
import javax.swing.AbstractAction;
import javax.swing.BoxLayout;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;

import nl.harmidi.harmonictable.TableModel;
import nl.harmidi.midi.MidiChannelFilterImpl;
import nl.harmidi.midi.MidiTranslator;
import nl.harmidi.midi.MyMidiEventsPrinter;
import nl.harmidi.midi.MyMidiEventsToTableListener;
import nl.harmidi.swing.LatencyPanel;
import nl.harmidi.swing.MidiOptionsPanel;
import nl.harmidi.swing.NoteOffDelayPanel;
import nl.harmidi.swing.TablePanel;
import nl.harmidi.util.Colors;
import nl.harmidi.util.MidiFileFilter;

import org.grlea.log.SimpleLogger;

/**
 * Main application and frame class.
 * 
 * @author R. Vries
 * 
 */
public class Application extends JFrame {
	private static final String FILE_OPEN_PATH_PREFERENCE = "fileOpenPath";

	private static final SimpleLogger log = new SimpleLogger(Application.class);

	private static final String LAST_OPENED_MIDIFILE_PREFERENCE = "midifile.default";

	private static final long serialVersionUID = 1L;

	private static final String SWITCH_PLAY_ACTION = "switchPlay";

	private static Application application;

	private static Sequencer sequencer;

	private File midiFile = null;

	private MenuItem playItem;

	private MenuItem stopItem;

	private JLabel statusBar = new JLabel();

	private MyMidiEventsToTableListener tableListener;

	private int channelToShow = 1;

	private JPanel tablePanel;

	private boolean printSequencerEvents = false;

	private final TableModel tableModel;

	public static void main(String... args) {
		application = new Application();
		application.setVisible(true);
	}

	public Application() {
		setTitle("Harmidi");
		tableModel = new TableModel();
		initMidi();
		initGui();
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		initInputListeners();
		reset();
		loadDefaultMidifile();
	}

	private void initInputListeners() {
		tablePanel.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), SWITCH_PLAY_ACTION);
		tablePanel.getActionMap().put(SWITCH_PLAY_ACTION, new SwitchPlayAction());

	}

	private void initMidi() {
		try {
			sequencer = MidiSystem.getSequencer();
			tableListener = new MyMidiEventsToTableListener(tableModel);
			tableListener.setFilter(new MidiChannelFilterImpl(channelToShow));
			sequencer.getTransmitter().setReceiver(new MidiTranslator(tableListener));
			if (printSequencerEvents) {
				sequencer.getTransmitter().setReceiver(new MidiTranslator(new MyMidiEventsPrinter()));
			}
		} catch (MidiUnavailableException e) {
			showError("Can't find a device to play midi");
			throw new RuntimeException(e);
		}
	}

	private void initGui() {

		// L&F
		setLookAndFeel();

		// frame general
		int width = Preferences.userRoot().getInt("frame.width", 1000);
		int height = Preferences.userRoot().getInt("frame.height", 800);
		setBounds(new Rectangle(width, height));

		setTitle("Harmidi");
		getContentPane().setLayout(new BorderLayout());
		JPanel centralizedFooter = new JPanel();
		centralizedFooter.setBackground(Colors.ORANGE);
		centralizedFooter.add(statusBar, BorderLayout.CENTER);
		getContentPane().add(centralizedFooter, BorderLayout.SOUTH);

		// menu
		initMenu();

		// Panels
		JPanel northPanel = new JPanel();
		northPanel.setLayout(new BoxLayout(northPanel, BoxLayout.X_AXIS));
		add(northPanel, BorderLayout.NORTH);

		northPanel.add(new LatencyPanel());
		northPanel.add(new MidiOptionsPanel(this));
		northPanel.add(new NoteOffDelayPanel(this));
		tablePanel = new TablePanel(tableModel);
		add(tablePanel, BorderLayout.CENTER);

	}

	private void initMenu() {

		MenuBar menuBar = new MenuBar();
		setMenuBar(menuBar);

		Menu fileMenu = new Menu("File");
		menuBar.add(fileMenu);
		final Application thisApp = this;
		MenuItem openItem = new MenuItem("Open...", new MenuShortcut(KeyEvent.VK_O));
		openItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				JFileChooser chooser = new JFileChooser();
				chooser.setFileFilter(new MidiFileFilter());
				chooser.setDialogTitle("select a midi file");
				String lastDir;
				try {
					lastDir = Preferences.userRoot().get(FILE_OPEN_PATH_PREFERENCE, new File(".").getCanonicalPath());
				} catch (IOException e2) {
					lastDir = ".";
				}
				if (lastDir.length() > 0) {
					chooser.setCurrentDirectory(new File(lastDir));
				}
				if (chooser.showOpenDialog(thisApp) == JFileChooser.APPROVE_OPTION) {
					midiFile = chooser.getSelectedFile();
					try {
						Preferences.userRoot().put(FILE_OPEN_PATH_PREFERENCE, chooser.getCurrentDirectory().getCanonicalPath());
					} catch (IOException e1) {
						log.warnException(e1);
					}
					loadFile(midiFile);
				}

			}
		});
		fileMenu.add(openItem);

		Menu transportMenu = new Menu("Transport");
		menuBar.add(transportMenu);
		playItem = new MenuItem("Play", new MenuShortcut(KeyEvent.VK_P));
		playItem.setEnabled(false);
		playItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				thisApp.playStart();
			}
		});
		transportMenu.add(playItem);

		MenuItem rewindItem = new MenuItem("Rewind", new MenuShortcut(KeyEvent.VK_R));
		rewindItem.setEnabled(true);
		rewindItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				thisApp.rewind();
			}
		});
		transportMenu.add(rewindItem);

		stopItem = new MenuItem("Stop", new MenuShortcut(KeyEvent.VK_S));
		stopItem.setEnabled(false);
		stopItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				thisApp.playStop();
			}
		});
		transportMenu.add(stopItem);

	}

	private void loadFile(File midiFile) {

		playStop();

		try {
			sequencer.setSequence(MidiSystem.getSequence(midiFile));
			playItem.setEnabled(true);
			setTitle("Harmidi - " + midiFile.getName());
			Preferences.userRoot().put(LAST_OPENED_MIDIFILE_PREFERENCE, midiFile.getCanonicalPath());
		} catch (InvalidMidiDataException e1) {
			showError("Invalid midi data, can't play");
			e1.printStackTrace();
		} catch (IOException e1) {
			showError("Input/output error");
			e1.printStackTrace();
		}
	}

	private void rewind() {
		if (midiFile != null && !sequencer.isRunning() && sequencer.isOpen()) {
			loadFile(midiFile);
		}
	}

	private void playStart() {
		if (!sequencer.isOpen()) {
			try {
				sequencer.open();
			} catch (Exception e) {
				showError("Problem opening the sequencer");
				log.warnException(e);
				return;
			}
		}

		if (!sequencer.isRunning()) {
			sequencer.start();
			playItem.setEnabled(false);
			stopItem.setEnabled(true);
			setStatus("Playing");
		}
	}

	private class SwitchPlayAction extends AbstractAction {
		private static final long serialVersionUID = 1L;

		public SwitchPlayAction() {
			super(SWITCH_PLAY_ACTION);
		}

		public void actionPerformed(ActionEvent e) {
			if (sequencer.isOpen() && sequencer.isRunning()) {
				playStop();
			} else {
				if (sequencer.getSequence() != null) {
					playStart();
				}
			}
		}
	}

	private void playStop() {
		if (sequencer.isRunning()) {
			sequencer.stop();
			playItem.setEnabled(true);
			stopItem.setEnabled(false);
			setStatus("Ready");
		}
	}

	private void setLookAndFeel() {

		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (Exception e) {
			log.debug("Couldn't set look and feel: " + e.getMessage());
		}
	}

	private void showError(String errorText) {
		JOptionPane.showMessageDialog(this, errorText);
	}

	private void setStatus(String text) {
		statusBar.setText(text);
	}

	private void reset() {
		setStatus("Ready");
	}

	private void loadDefaultMidifile() {
		try {
			final String lastMidifile = Preferences.userRoot().get(LAST_OPENED_MIDIFILE_PREFERENCE, null);
			if (lastMidifile != null) {
				midiFile = new File(lastMidifile);
				loadFile(midiFile);
			}
		} catch (Exception e) {
			log.errorException(e);
		}
	}

	public int getChannelToShow() {
		return channelToShow;
	}

	public void setChannelToShow(int channelToShow) {
		log.debugObject("new channel", channelToShow);
		this.channelToShow = channelToShow;
		tableListener.setFilter(new MidiChannelFilterImpl(channelToShow));
		tableModel.reset();
	}

	public void setNewDelay(int newDelay) {
		tableListener.setNoteOffDelay(newDelay * 1000L);
	}

}
